package org.budo.support.spring.bean.resolver;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.budo.support.javassist.util.JavassistUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.spring.context.aware.BudoApplicationContextAware;
import org.budo.support.spring.util.SpringUtil;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * 生成一个类，可在运行时调用此类的静态方法获取Bean，可用于模板引擎
 * 
 * @author lmw
 */
@Slf4j
@Getter
@Setter
public class BudoStaticBeanResolverGenerator implements ApplicationListener<ContextRefreshedEvent> {
    private String[] packages = new String[] { "org.budo.module.*.web.api" };

    private String className = "api.Sdk";

    private Class<?> superType = this.getClass();

    private boolean isInPackages(String packageName) {
        for (String pkg : this.packages) {
            int a = pkg.indexOf("*");
            if (a < 0) { // 没有星号
                return packageName.startsWith(pkg);
            }

            int b = pkg.indexOf("*", a + 1);
            if (b >= 0) {
                throw new IllegalArgumentException("#49 pkg=" + pkg); // 只支持一个星号
            }

            if (a == 0) { // 星号开始
                String right = pkg.substring(1);
                return packageName.endsWith(right);
            }

            if (a == pkg.length() - 1) { // 星号结束
                String left = pkg.substring(0, pkg.length() - 1);
                return packageName.startsWith(left);
            }

            // 中间星号
            String left = pkg.substring(0, a);
            String right = pkg.substring(a + 1, pkg.length());
            return packageName.startsWith(left) && packageName.endsWith(right);
        }

        return false;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (ReflectUtil.hasClass(this.getClassName())) {
            log.warn("#39 postProcessBeanFactory, " + this.getClassName() + " exists now, return");
            return;
        }

        this.generate();
    }

    private void _do_generate() {
        ClassPool classPool = ClassPool.getDefault();

        CtClass ctClass = classPool.makeClass(this.getClassName());

        if (null != this.getSuperType()) {
            classPool.insertClassPath(new ClassClassPath(this.getSuperType()));
            CtClass superClass = JavassistUtil.get(classPool, this.getSuperType().getName());
            JavassistUtil.setSuperclass(ctClass, superClass);
        }

        Set<Class<?>> interfaces = this.interfaces();

        long start = System.currentTimeMillis();
        for (Class<?> interfaceType : interfaces) {
            String interfaceTypeName = interfaceType.getName();
            if (!this.isInPackages(interfaceType.getPackage().getName())) {
                log.debug("#100 sdk continue, interfaceTypeName=" + interfaceTypeName);
                continue;
            }

            log.debug("#104 sdk add interfaceType=" + interfaceType);

            classPool.insertClassPath(new ClassClassPath(interfaceType));
            String methodSrc = "public static " + interfaceType.getName() + " " + interfaceType.getSimpleName() + "() { " //
                    + "    return org.budo.support.spring.context.aware.BudoApplicationContextAware.getBean(" + interfaceType.getName() + ".class); " //
                    + " } ";
            JavassistUtil.addMethod(ctClass, JavassistUtil.make(methodSrc, ctClass));
        }

        JavassistUtil.toClass(ctClass, this.getClass().getClassLoader(), this.getClass().getProtectionDomain());

        Class<?> type = ReflectUtil.classForName(this.getClassName());

        log.info("#113 type=" + type + ", cost time " + (System.currentTimeMillis() - start) + " ms");
    }

    private Set<Class<?>> interfaces() {
        Set<Class<?>> interfaces = new HashSet<Class<?>>();
        BudoApplicationContextAware applicationContextAware = BudoApplicationContextAware.getInstanceNoNull();
        ApplicationContext applicationContext = applicationContextAware.getApplicationContext();
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();

        for (String beanName : beanDefinitionNames) {
            BeanDefinition beanDefinition = SpringUtil.getBeanDefinition(applicationContext, beanName);

            String beanClassName = beanDefinition.getBeanClassName();
            if (null == beanClassName //
                    || beanClassName.equals("com.alibaba.dubbo.config.spring.ReferenceBean") // 没必要 且较耗时
                    || beanClassName.equals("org.budo.spring.cache.interceptor.BudoSpringCacheInterceptor")) {
                log.warn("#98 continue, beanClassName=" + beanClassName + ", propertyValues=" + beanDefinition.getPropertyValues());
                continue;
            }

            Object beanInstance = SpringUtil.getBean(applicationContext, beanName);
            if (null == beanInstance) {
                log.warn("#104 beanInstance is null or abstract, beanName=" + beanName + ", applicationContext=" + applicationContext + ", beanDefinition=" + beanDefinition);
                continue;
            }

            log.debug("#108 beanInstance=" + beanInstance + ", beanName=" + beanName);
            List<Class<?>> list = ReflectUtil.getAllInterfaces(beanInstance.getClass());
            interfaces.addAll(list);
        }

        return interfaces;
    }

    /**
     * 异步，开发环境执行太慢了
     */
    private void generate() {
        long start = System.currentTimeMillis();
        _do_generate();

        log.info("#115 after doGenerate, time cost " + (System.currentTimeMillis() - start) + " ms");
    }
}
